plot.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234
  1. """Plotting module for SymPy.
  2. A plot is represented by the ``Plot`` class that contains a reference to the
  3. backend and a list of the data series to be plotted. The data series are
  4. instances of classes meant to simplify getting points and meshes from SymPy
  5. expressions. ``plot_backends`` is a dictionary with all the backends.
  6. This module gives only the essential. For all the fancy stuff use directly
  7. the backend. You can get the backend wrapper for every plot from the
  8. ``_backend`` attribute. Moreover the data series classes have various useful
  9. methods like ``get_points``, ``get_meshes``, etc, that may
  10. be useful if you wish to use another plotting library.
  11. Especially if you need publication ready graphs and this module is not enough
  12. for you - just get the ``_backend`` attribute and add whatever you want
  13. directly to it. In the case of matplotlib (the common way to graph data in
  14. python) just copy ``_backend.fig`` which is the figure and ``_backend.ax``
  15. which is the axis and work on them as you would on any other matplotlib object.
  16. Simplicity of code takes much greater importance than performance. Do not use it
  17. if you care at all about performance. A new backend instance is initialized
  18. every time you call ``show()`` and the old one is left to the garbage collector.
  19. """
  20. from sympy.concrete.summations import Sum
  21. from sympy.core.containers import Tuple
  22. from sympy.core.expr import Expr
  23. from sympy.core.function import Function, AppliedUndef
  24. from sympy.core.symbol import (Dummy, Symbol, Wild)
  25. from sympy.external import import_module
  26. from sympy.functions import sign
  27. from sympy.plotting.backends.base_backend import Plot
  28. from sympy.plotting.backends.matplotlibbackend import MatplotlibBackend
  29. from sympy.plotting.backends.textbackend import TextBackend
  30. from sympy.plotting.series import (
  31. LineOver1DRangeSeries, Parametric2DLineSeries, Parametric3DLineSeries,
  32. ParametricSurfaceSeries, SurfaceOver2DRangeSeries, ContourSeries)
  33. from sympy.plotting.utils import _check_arguments, _plot_sympify
  34. from sympy.tensor.indexed import Indexed
  35. # to maintain back-compatibility
  36. from sympy.plotting.plotgrid import PlotGrid # noqa: F401
  37. from sympy.plotting.series import BaseSeries # noqa: F401
  38. from sympy.plotting.series import Line2DBaseSeries # noqa: F401
  39. from sympy.plotting.series import Line3DBaseSeries # noqa: F401
  40. from sympy.plotting.series import SurfaceBaseSeries # noqa: F401
  41. from sympy.plotting.series import List2DSeries # noqa: F401
  42. from sympy.plotting.series import GenericDataSeries # noqa: F401
  43. from sympy.plotting.series import centers_of_faces # noqa: F401
  44. from sympy.plotting.series import centers_of_segments # noqa: F401
  45. from sympy.plotting.series import flat # noqa: F401
  46. from sympy.plotting.backends.base_backend import unset_show # noqa: F401
  47. from sympy.plotting.backends.matplotlibbackend import _matplotlib_list # noqa: F401
  48. from sympy.plotting.textplot import textplot # noqa: F401
  49. __doctest_requires__ = {
  50. ('plot3d',
  51. 'plot3d_parametric_line',
  52. 'plot3d_parametric_surface',
  53. 'plot_parametric'): ['matplotlib'],
  54. # XXX: The plot doctest possibly should not require matplotlib. It fails at
  55. # plot(x**2, (x, -5, 5)) which should be fine for text backend.
  56. ('plot',): ['matplotlib'],
  57. }
  58. def _process_summations(sum_bound, *args):
  59. """Substitute oo (infinity) in the lower/upper bounds of a summation with
  60. some integer number.
  61. Parameters
  62. ==========
  63. sum_bound : int
  64. oo will be substituted with this integer number.
  65. *args : list/tuple
  66. pre-processed arguments of the form (expr, range, ...)
  67. Notes
  68. =====
  69. Let's consider the following summation: ``Sum(1 / x**2, (x, 1, oo))``.
  70. The current implementation of lambdify (SymPy 1.12 at the time of
  71. writing this) will create something of this form:
  72. ``sum(1 / x**2 for x in range(1, INF))``
  73. The problem is that ``type(INF)`` is float, while ``range`` requires
  74. integers: the evaluation fails.
  75. Instead of modifying ``lambdify`` (which requires a deep knowledge), just
  76. replace it with some integer number.
  77. """
  78. def new_bound(t, bound):
  79. if (not t.is_number) or t.is_finite:
  80. return t
  81. if sign(t) >= 0:
  82. return bound
  83. return -bound
  84. args = list(args)
  85. expr = args[0]
  86. # select summations whose lower/upper bound is infinity
  87. w = Wild("w", properties=[
  88. lambda t: isinstance(t, Sum),
  89. lambda t: any((not a[1].is_finite) or (not a[2].is_finite) for i, a in enumerate(t.args) if i > 0)
  90. ])
  91. for t in list(expr.find(w)):
  92. sums_args = list(t.args)
  93. for i, a in enumerate(sums_args):
  94. if i > 0:
  95. sums_args[i] = (a[0], new_bound(a[1], sum_bound),
  96. new_bound(a[2], sum_bound))
  97. s = Sum(*sums_args)
  98. expr = expr.subs(t, s)
  99. args[0] = expr
  100. return args
  101. def _build_line_series(*args, **kwargs):
  102. """Loop over the provided arguments and create the necessary line series.
  103. """
  104. series = []
  105. sum_bound = int(kwargs.get("sum_bound", 1000))
  106. for arg in args:
  107. expr, r, label, rendering_kw = arg
  108. kw = kwargs.copy()
  109. if rendering_kw is not None:
  110. kw["rendering_kw"] = rendering_kw
  111. # TODO: _process_piecewise check goes here
  112. if not callable(expr):
  113. arg = _process_summations(sum_bound, *arg)
  114. series.append(LineOver1DRangeSeries(*arg[:-1], **kw))
  115. return series
  116. def _create_series(series_type, plot_expr, **kwargs):
  117. """Extract the rendering_kw dictionary from the provided arguments and
  118. create an appropriate data series.
  119. """
  120. series = []
  121. for args in plot_expr:
  122. kw = kwargs.copy()
  123. if args[-1] is not None:
  124. kw["rendering_kw"] = args[-1]
  125. series.append(series_type(*args[:-1], **kw))
  126. return series
  127. def _set_labels(series, labels, rendering_kw):
  128. """Apply the `label` and `rendering_kw` keyword arguments to the series.
  129. """
  130. if not isinstance(labels, (list, tuple)):
  131. labels = [labels]
  132. if len(labels) > 0:
  133. if len(labels) == 1 and len(series) > 1:
  134. # if one label is provided and multiple series are being plotted,
  135. # set the same label to all data series. It maintains
  136. # back-compatibility
  137. labels *= len(series)
  138. if len(series) != len(labels):
  139. raise ValueError("The number of labels must be equal to the "
  140. "number of expressions being plotted.\nReceived "
  141. f"{len(series)} expressions and {len(labels)} labels")
  142. for s, l in zip(series, labels):
  143. s.label = l
  144. if rendering_kw:
  145. if isinstance(rendering_kw, dict):
  146. rendering_kw = [rendering_kw]
  147. if len(rendering_kw) == 1:
  148. rendering_kw *= len(series)
  149. elif len(series) != len(rendering_kw):
  150. raise ValueError("The number of rendering dictionaries must be "
  151. "equal to the number of expressions being plotted.\nReceived "
  152. f"{len(series)} expressions and {len(labels)} labels")
  153. for s, r in zip(series, rendering_kw):
  154. s.rendering_kw = r
  155. def plot_factory(*args, **kwargs):
  156. backend = kwargs.pop("backend", "default")
  157. if isinstance(backend, str):
  158. if backend == "default":
  159. matplotlib = import_module('matplotlib',
  160. min_module_version='1.1.0', catch=(RuntimeError,))
  161. if matplotlib:
  162. return MatplotlibBackend(*args, **kwargs)
  163. return TextBackend(*args, **kwargs)
  164. return plot_backends[backend](*args, **kwargs)
  165. elif (type(backend) == type) and issubclass(backend, Plot):
  166. return backend(*args, **kwargs)
  167. else:
  168. raise TypeError("backend must be either a string or a subclass of ``Plot``.")
  169. plot_backends = {
  170. 'matplotlib': MatplotlibBackend,
  171. 'text': TextBackend,
  172. }
  173. ####New API for plotting module ####
  174. # TODO: Add color arrays for plots.
  175. # TODO: Add more plotting options for 3d plots.
  176. # TODO: Adaptive sampling for 3D plots.
  177. def plot(*args, show=True, **kwargs):
  178. """Plots a function of a single variable as a curve.
  179. Parameters
  180. ==========
  181. args :
  182. The first argument is the expression representing the function
  183. of single variable to be plotted.
  184. The last argument is a 3-tuple denoting the range of the free
  185. variable. e.g. ``(x, 0, 5)``
  186. Typical usage examples are in the following:
  187. - Plotting a single expression with a single range.
  188. ``plot(expr, range, **kwargs)``
  189. - Plotting a single expression with the default range (-10, 10).
  190. ``plot(expr, **kwargs)``
  191. - Plotting multiple expressions with a single range.
  192. ``plot(expr1, expr2, ..., range, **kwargs)``
  193. - Plotting multiple expressions with multiple ranges.
  194. ``plot((expr1, range1), (expr2, range2), ..., **kwargs)``
  195. It is best practice to specify range explicitly because default
  196. range may change in the future if a more advanced default range
  197. detection algorithm is implemented.
  198. show : bool, optional
  199. The default value is set to ``True``. Set show to ``False`` and
  200. the function will not display the plot. The returned instance of
  201. the ``Plot`` class can then be used to save or display the plot
  202. by calling the ``save()`` and ``show()`` methods respectively.
  203. line_color : string, or float, or function, optional
  204. Specifies the color for the plot.
  205. See ``Plot`` to see how to set color for the plots.
  206. Note that by setting ``line_color``, it would be applied simultaneously
  207. to all the series.
  208. title : str, optional
  209. Title of the plot. It is set to the latex representation of
  210. the expression, if the plot has only one expression.
  211. label : str, optional
  212. The label of the expression in the plot. It will be used when
  213. called with ``legend``. Default is the name of the expression.
  214. e.g. ``sin(x)``
  215. xlabel : str or expression, optional
  216. Label for the x-axis.
  217. ylabel : str or expression, optional
  218. Label for the y-axis.
  219. xscale : 'linear' or 'log', optional
  220. Sets the scaling of the x-axis.
  221. yscale : 'linear' or 'log', optional
  222. Sets the scaling of the y-axis.
  223. axis_center : (float, float), optional
  224. Tuple of two floats denoting the coordinates of the center or
  225. {'center', 'auto'}
  226. xlim : (float, float), optional
  227. Denotes the x-axis limits, ``(min, max)```.
  228. ylim : (float, float), optional
  229. Denotes the y-axis limits, ``(min, max)```.
  230. annotations : list, optional
  231. A list of dictionaries specifying the type of annotation
  232. required. The keys in the dictionary should be equivalent
  233. to the arguments of the :external:mod:`matplotlib`'s
  234. :external:meth:`~matplotlib.axes.Axes.annotate` method.
  235. markers : list, optional
  236. A list of dictionaries specifying the type the markers required.
  237. The keys in the dictionary should be equivalent to the arguments
  238. of the :external:mod:`matplotlib`'s :external:func:`~matplotlib.pyplot.plot()` function
  239. along with the marker related keyworded arguments.
  240. rectangles : list, optional
  241. A list of dictionaries specifying the dimensions of the
  242. rectangles to be plotted. The keys in the dictionary should be
  243. equivalent to the arguments of the :external:mod:`matplotlib`'s
  244. :external:class:`~matplotlib.patches.Rectangle` class.
  245. fill : dict, optional
  246. A dictionary specifying the type of color filling required in
  247. the plot. The keys in the dictionary should be equivalent to the
  248. arguments of the :external:mod:`matplotlib`'s
  249. :external:meth:`~matplotlib.axes.Axes.fill_between` method.
  250. adaptive : bool, optional
  251. The default value for the ``adaptive`` parameter is now ``False``.
  252. To enable adaptive sampling, set ``adaptive=True`` and specify ``n`` if uniform sampling is required.
  253. The plotting uses an adaptive algorithm which samples
  254. recursively to accurately plot. The adaptive algorithm uses a
  255. random point near the midpoint of two points that has to be
  256. further sampled. Hence the same plots can appear slightly
  257. different.
  258. depth : int, optional
  259. Recursion depth of the adaptive algorithm. A depth of value
  260. `n` samples a maximum of `2^{n}` points.
  261. If the ``adaptive`` flag is set to ``False``, this will be
  262. ignored.
  263. n : int, optional
  264. Used when the ``adaptive`` is set to ``False``. The function
  265. is uniformly sampled at ``n`` number of points. If the ``adaptive``
  266. flag is set to ``True``, this will be ignored.
  267. This keyword argument replaces ``nb_of_points``, which should be
  268. considered deprecated.
  269. size : (float, float), optional
  270. A tuple in the form (width, height) in inches to specify the size of
  271. the overall figure. The default value is set to ``None``, meaning
  272. the size will be set by the default backend.
  273. Examples
  274. ========
  275. .. plot::
  276. :context: close-figs
  277. :format: doctest
  278. :include-source: True
  279. >>> from sympy import symbols
  280. >>> from sympy.plotting import plot
  281. >>> x = symbols('x')
  282. Single Plot
  283. .. plot::
  284. :context: close-figs
  285. :format: doctest
  286. :include-source: True
  287. >>> plot(x**2, (x, -5, 5))
  288. Plot object containing:
  289. [0]: cartesian line: x**2 for x over (-5.0, 5.0)
  290. Multiple plots with single range.
  291. .. plot::
  292. :context: close-figs
  293. :format: doctest
  294. :include-source: True
  295. >>> plot(x, x**2, x**3, (x, -5, 5))
  296. Plot object containing:
  297. [0]: cartesian line: x for x over (-5.0, 5.0)
  298. [1]: cartesian line: x**2 for x over (-5.0, 5.0)
  299. [2]: cartesian line: x**3 for x over (-5.0, 5.0)
  300. Multiple plots with different ranges.
  301. .. plot::
  302. :context: close-figs
  303. :format: doctest
  304. :include-source: True
  305. >>> plot((x**2, (x, -6, 6)), (x, (x, -5, 5)))
  306. Plot object containing:
  307. [0]: cartesian line: x**2 for x over (-6.0, 6.0)
  308. [1]: cartesian line: x for x over (-5.0, 5.0)
  309. No adaptive sampling by default. If adaptive sampling is required, set ``adaptive=True``.
  310. .. plot::
  311. :context: close-figs
  312. :format: doctest
  313. :include-source: True
  314. >>> plot(x**2, adaptive=True, n=400)
  315. Plot object containing:
  316. [0]: cartesian line: x**2 for x over (-10.0, 10.0)
  317. See Also
  318. ========
  319. Plot, LineOver1DRangeSeries
  320. """
  321. args = _plot_sympify(args)
  322. plot_expr = _check_arguments(args, 1, 1, **kwargs)
  323. params = kwargs.get("params", None)
  324. free = set()
  325. for p in plot_expr:
  326. if not isinstance(p[1][0], str):
  327. free |= {p[1][0]}
  328. else:
  329. free |= {Symbol(p[1][0])}
  330. if params:
  331. free = free.difference(params.keys())
  332. x = free.pop() if free else Symbol("x")
  333. kwargs.setdefault('xlabel', x)
  334. kwargs.setdefault('ylabel', Function('f')(x))
  335. labels = kwargs.pop("label", [])
  336. rendering_kw = kwargs.pop("rendering_kw", None)
  337. series = _build_line_series(*plot_expr, **kwargs)
  338. _set_labels(series, labels, rendering_kw)
  339. plots = plot_factory(*series, **kwargs)
  340. if show:
  341. plots.show()
  342. return plots
  343. def plot_parametric(*args, show=True, **kwargs):
  344. """
  345. Plots a 2D parametric curve.
  346. Parameters
  347. ==========
  348. args
  349. Common specifications are:
  350. - Plotting a single parametric curve with a range
  351. ``plot_parametric((expr_x, expr_y), range)``
  352. - Plotting multiple parametric curves with the same range
  353. ``plot_parametric((expr_x, expr_y), ..., range)``
  354. - Plotting multiple parametric curves with different ranges
  355. ``plot_parametric((expr_x, expr_y, range), ...)``
  356. ``expr_x`` is the expression representing $x$ component of the
  357. parametric function.
  358. ``expr_y`` is the expression representing $y$ component of the
  359. parametric function.
  360. ``range`` is a 3-tuple denoting the parameter symbol, start and
  361. stop. For example, ``(u, 0, 5)``.
  362. If the range is not specified, then a default range of (-10, 10)
  363. is used.
  364. However, if the arguments are specified as
  365. ``(expr_x, expr_y, range), ...``, you must specify the ranges
  366. for each expressions manually.
  367. Default range may change in the future if a more advanced
  368. algorithm is implemented.
  369. adaptive : bool, optional
  370. Specifies whether to use the adaptive sampling or not.
  371. The default value is set to ``True``. Set adaptive to ``False``
  372. and specify ``n`` if uniform sampling is required.
  373. depth : int, optional
  374. The recursion depth of the adaptive algorithm. A depth of
  375. value $n$ samples a maximum of $2^n$ points.
  376. n : int, optional
  377. Used when the ``adaptive`` flag is set to ``False``. Specifies the
  378. number of the points used for the uniform sampling.
  379. This keyword argument replaces ``nb_of_points``, which should be
  380. considered deprecated.
  381. line_color : string, or float, or function, optional
  382. Specifies the color for the plot.
  383. See ``Plot`` to see how to set color for the plots.
  384. Note that by setting ``line_color``, it would be applied simultaneously
  385. to all the series.
  386. label : str, optional
  387. The label of the expression in the plot. It will be used when
  388. called with ``legend``. Default is the name of the expression.
  389. e.g. ``sin(x)``
  390. xlabel : str, optional
  391. Label for the x-axis.
  392. ylabel : str, optional
  393. Label for the y-axis.
  394. xscale : 'linear' or 'log', optional
  395. Sets the scaling of the x-axis.
  396. yscale : 'linear' or 'log', optional
  397. Sets the scaling of the y-axis.
  398. axis_center : (float, float), optional
  399. Tuple of two floats denoting the coordinates of the center or
  400. {'center', 'auto'}
  401. xlim : (float, float), optional
  402. Denotes the x-axis limits, ``(min, max)```.
  403. ylim : (float, float), optional
  404. Denotes the y-axis limits, ``(min, max)```.
  405. size : (float, float), optional
  406. A tuple in the form (width, height) in inches to specify the size of
  407. the overall figure. The default value is set to ``None``, meaning
  408. the size will be set by the default backend.
  409. Examples
  410. ========
  411. .. plot::
  412. :context: reset
  413. :format: doctest
  414. :include-source: True
  415. >>> from sympy import plot_parametric, symbols, cos, sin
  416. >>> u = symbols('u')
  417. A parametric plot with a single expression:
  418. .. plot::
  419. :context: close-figs
  420. :format: doctest
  421. :include-source: True
  422. >>> plot_parametric((cos(u), sin(u)), (u, -5, 5))
  423. Plot object containing:
  424. [0]: parametric cartesian line: (cos(u), sin(u)) for u over (-5.0, 5.0)
  425. A parametric plot with multiple expressions with the same range:
  426. .. plot::
  427. :context: close-figs
  428. :format: doctest
  429. :include-source: True
  430. >>> plot_parametric((cos(u), sin(u)), (u, cos(u)), (u, -10, 10))
  431. Plot object containing:
  432. [0]: parametric cartesian line: (cos(u), sin(u)) for u over (-10.0, 10.0)
  433. [1]: parametric cartesian line: (u, cos(u)) for u over (-10.0, 10.0)
  434. A parametric plot with multiple expressions with different ranges
  435. for each curve:
  436. .. plot::
  437. :context: close-figs
  438. :format: doctest
  439. :include-source: True
  440. >>> plot_parametric((cos(u), sin(u), (u, -5, 5)),
  441. ... (cos(u), u, (u, -5, 5)))
  442. Plot object containing:
  443. [0]: parametric cartesian line: (cos(u), sin(u)) for u over (-5.0, 5.0)
  444. [1]: parametric cartesian line: (cos(u), u) for u over (-5.0, 5.0)
  445. Notes
  446. =====
  447. The plotting uses an adaptive algorithm which samples recursively to
  448. accurately plot the curve. The adaptive algorithm uses a random point
  449. near the midpoint of two points that has to be further sampled.
  450. Hence, repeating the same plot command can give slightly different
  451. results because of the random sampling.
  452. If there are multiple plots, then the same optional arguments are
  453. applied to all the plots drawn in the same canvas. If you want to
  454. set these options separately, you can index the returned ``Plot``
  455. object and set it.
  456. For example, when you specify ``line_color`` once, it would be
  457. applied simultaneously to both series.
  458. .. plot::
  459. :context: close-figs
  460. :format: doctest
  461. :include-source: True
  462. >>> from sympy import pi
  463. >>> expr1 = (u, cos(2*pi*u)/2 + 1/2)
  464. >>> expr2 = (u, sin(2*pi*u)/2 + 1/2)
  465. >>> p = plot_parametric(expr1, expr2, (u, 0, 1), line_color='blue')
  466. If you want to specify the line color for the specific series, you
  467. should index each item and apply the property manually.
  468. .. plot::
  469. :context: close-figs
  470. :format: doctest
  471. :include-source: True
  472. >>> p[0].line_color = 'red'
  473. >>> p.show()
  474. See Also
  475. ========
  476. Plot, Parametric2DLineSeries
  477. """
  478. args = _plot_sympify(args)
  479. plot_expr = _check_arguments(args, 2, 1, **kwargs)
  480. labels = kwargs.pop("label", [])
  481. rendering_kw = kwargs.pop("rendering_kw", None)
  482. series = _create_series(Parametric2DLineSeries, plot_expr, **kwargs)
  483. _set_labels(series, labels, rendering_kw)
  484. plots = plot_factory(*series, **kwargs)
  485. if show:
  486. plots.show()
  487. return plots
  488. def plot3d_parametric_line(*args, show=True, **kwargs):
  489. """
  490. Plots a 3D parametric line plot.
  491. Usage
  492. =====
  493. Single plot:
  494. ``plot3d_parametric_line(expr_x, expr_y, expr_z, range, **kwargs)``
  495. If the range is not specified, then a default range of (-10, 10) is used.
  496. Multiple plots.
  497. ``plot3d_parametric_line((expr_x, expr_y, expr_z, range), ..., **kwargs)``
  498. Ranges have to be specified for every expression.
  499. Default range may change in the future if a more advanced default range
  500. detection algorithm is implemented.
  501. Arguments
  502. =========
  503. expr_x : Expression representing the function along x.
  504. expr_y : Expression representing the function along y.
  505. expr_z : Expression representing the function along z.
  506. range : (:class:`~.Symbol`, float, float)
  507. A 3-tuple denoting the range of the parameter variable, e.g., (u, 0, 5).
  508. Keyword Arguments
  509. =================
  510. Arguments for ``Parametric3DLineSeries`` class.
  511. n : int
  512. The range is uniformly sampled at ``n`` number of points.
  513. This keyword argument replaces ``nb_of_points``, which should be
  514. considered deprecated.
  515. Aesthetics:
  516. line_color : string, or float, or function, optional
  517. Specifies the color for the plot.
  518. See ``Plot`` to see how to set color for the plots.
  519. Note that by setting ``line_color``, it would be applied simultaneously
  520. to all the series.
  521. label : str
  522. The label to the plot. It will be used when called with ``legend=True``
  523. to denote the function with the given label in the plot.
  524. If there are multiple plots, then the same series arguments are applied to
  525. all the plots. If you want to set these options separately, you can index
  526. the returned ``Plot`` object and set it.
  527. Arguments for ``Plot`` class.
  528. title : str
  529. Title of the plot.
  530. size : (float, float), optional
  531. A tuple in the form (width, height) in inches to specify the size of
  532. the overall figure. The default value is set to ``None``, meaning
  533. the size will be set by the default backend.
  534. Examples
  535. ========
  536. .. plot::
  537. :context: reset
  538. :format: doctest
  539. :include-source: True
  540. >>> from sympy import symbols, cos, sin
  541. >>> from sympy.plotting import plot3d_parametric_line
  542. >>> u = symbols('u')
  543. Single plot.
  544. .. plot::
  545. :context: close-figs
  546. :format: doctest
  547. :include-source: True
  548. >>> plot3d_parametric_line(cos(u), sin(u), u, (u, -5, 5))
  549. Plot object containing:
  550. [0]: 3D parametric cartesian line: (cos(u), sin(u), u) for u over (-5.0, 5.0)
  551. Multiple plots.
  552. .. plot::
  553. :context: close-figs
  554. :format: doctest
  555. :include-source: True
  556. >>> plot3d_parametric_line((cos(u), sin(u), u, (u, -5, 5)),
  557. ... (sin(u), u**2, u, (u, -5, 5)))
  558. Plot object containing:
  559. [0]: 3D parametric cartesian line: (cos(u), sin(u), u) for u over (-5.0, 5.0)
  560. [1]: 3D parametric cartesian line: (sin(u), u**2, u) for u over (-5.0, 5.0)
  561. See Also
  562. ========
  563. Plot, Parametric3DLineSeries
  564. """
  565. args = _plot_sympify(args)
  566. plot_expr = _check_arguments(args, 3, 1, **kwargs)
  567. kwargs.setdefault("xlabel", "x")
  568. kwargs.setdefault("ylabel", "y")
  569. kwargs.setdefault("zlabel", "z")
  570. labels = kwargs.pop("label", [])
  571. rendering_kw = kwargs.pop("rendering_kw", None)
  572. series = _create_series(Parametric3DLineSeries, plot_expr, **kwargs)
  573. _set_labels(series, labels, rendering_kw)
  574. plots = plot_factory(*series, **kwargs)
  575. if show:
  576. plots.show()
  577. return plots
  578. def _plot3d_plot_contour_helper(Series, *args, **kwargs):
  579. """plot3d and plot_contour are structurally identical. Let's reduce
  580. code repetition.
  581. """
  582. # NOTE: if this import would be at the top-module level, it would trigger
  583. # SymPy's optional-dependencies tests to fail.
  584. from sympy.vector import BaseScalar
  585. args = _plot_sympify(args)
  586. plot_expr = _check_arguments(args, 1, 2, **kwargs)
  587. free_x = set()
  588. free_y = set()
  589. _types = (Symbol, BaseScalar, Indexed, AppliedUndef)
  590. for p in plot_expr:
  591. free_x |= {p[1][0]} if isinstance(p[1][0], _types) else {Symbol(p[1][0])}
  592. free_y |= {p[2][0]} if isinstance(p[2][0], _types) else {Symbol(p[2][0])}
  593. x = free_x.pop() if free_x else Symbol("x")
  594. y = free_y.pop() if free_y else Symbol("y")
  595. kwargs.setdefault("xlabel", x)
  596. kwargs.setdefault("ylabel", y)
  597. kwargs.setdefault("zlabel", Function('f')(x, y))
  598. # if a polar discretization is requested and automatic labelling has ben
  599. # applied, hide the labels on the x-y axis.
  600. if kwargs.get("is_polar", False):
  601. if callable(kwargs["xlabel"]):
  602. kwargs["xlabel"] = ""
  603. if callable(kwargs["ylabel"]):
  604. kwargs["ylabel"] = ""
  605. labels = kwargs.pop("label", [])
  606. rendering_kw = kwargs.pop("rendering_kw", None)
  607. series = _create_series(Series, plot_expr, **kwargs)
  608. _set_labels(series, labels, rendering_kw)
  609. plots = plot_factory(*series, **kwargs)
  610. if kwargs.get("show", True):
  611. plots.show()
  612. return plots
  613. def plot3d(*args, show=True, **kwargs):
  614. """
  615. Plots a 3D surface plot.
  616. Usage
  617. =====
  618. Single plot
  619. ``plot3d(expr, range_x, range_y, **kwargs)``
  620. If the ranges are not specified, then a default range of (-10, 10) is used.
  621. Multiple plot with the same range.
  622. ``plot3d(expr1, expr2, range_x, range_y, **kwargs)``
  623. If the ranges are not specified, then a default range of (-10, 10) is used.
  624. Multiple plots with different ranges.
  625. ``plot3d((expr1, range_x, range_y), (expr2, range_x, range_y), ..., **kwargs)``
  626. Ranges have to be specified for every expression.
  627. Default range may change in the future if a more advanced default range
  628. detection algorithm is implemented.
  629. Arguments
  630. =========
  631. expr : Expression representing the function along x.
  632. range_x : (:class:`~.Symbol`, float, float)
  633. A 3-tuple denoting the range of the x variable, e.g. (x, 0, 5).
  634. range_y : (:class:`~.Symbol`, float, float)
  635. A 3-tuple denoting the range of the y variable, e.g. (y, 0, 5).
  636. Keyword Arguments
  637. =================
  638. Arguments for ``SurfaceOver2DRangeSeries`` class:
  639. n1 : int
  640. The x range is sampled uniformly at ``n1`` of points.
  641. This keyword argument replaces ``nb_of_points_x``, which should be
  642. considered deprecated.
  643. n2 : int
  644. The y range is sampled uniformly at ``n2`` of points.
  645. This keyword argument replaces ``nb_of_points_y``, which should be
  646. considered deprecated.
  647. Aesthetics:
  648. surface_color : Function which returns a float
  649. Specifies the color for the surface of the plot.
  650. See :class:`~.Plot` for more details.
  651. If there are multiple plots, then the same series arguments are applied to
  652. all the plots. If you want to set these options separately, you can index
  653. the returned ``Plot`` object and set it.
  654. Arguments for ``Plot`` class:
  655. title : str
  656. Title of the plot.
  657. size : (float, float), optional
  658. A tuple in the form (width, height) in inches to specify the size of the
  659. overall figure. The default value is set to ``None``, meaning the size will
  660. be set by the default backend.
  661. Examples
  662. ========
  663. .. plot::
  664. :context: reset
  665. :format: doctest
  666. :include-source: True
  667. >>> from sympy import symbols
  668. >>> from sympy.plotting import plot3d
  669. >>> x, y = symbols('x y')
  670. Single plot
  671. .. plot::
  672. :context: close-figs
  673. :format: doctest
  674. :include-source: True
  675. >>> plot3d(x*y, (x, -5, 5), (y, -5, 5))
  676. Plot object containing:
  677. [0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
  678. Multiple plots with same range
  679. .. plot::
  680. :context: close-figs
  681. :format: doctest
  682. :include-source: True
  683. >>> plot3d(x*y, -x*y, (x, -5, 5), (y, -5, 5))
  684. Plot object containing:
  685. [0]: cartesian surface: x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
  686. [1]: cartesian surface: -x*y for x over (-5.0, 5.0) and y over (-5.0, 5.0)
  687. Multiple plots with different ranges.
  688. .. plot::
  689. :context: close-figs
  690. :format: doctest
  691. :include-source: True
  692. >>> plot3d((x**2 + y**2, (x, -5, 5), (y, -5, 5)),
  693. ... (x*y, (x, -3, 3), (y, -3, 3)))
  694. Plot object containing:
  695. [0]: cartesian surface: x**2 + y**2 for x over (-5.0, 5.0) and y over (-5.0, 5.0)
  696. [1]: cartesian surface: x*y for x over (-3.0, 3.0) and y over (-3.0, 3.0)
  697. See Also
  698. ========
  699. Plot, SurfaceOver2DRangeSeries
  700. """
  701. kwargs.setdefault("show", show)
  702. return _plot3d_plot_contour_helper(
  703. SurfaceOver2DRangeSeries, *args, **kwargs)
  704. def plot3d_parametric_surface(*args, show=True, **kwargs):
  705. """
  706. Plots a 3D parametric surface plot.
  707. Explanation
  708. ===========
  709. Single plot.
  710. ``plot3d_parametric_surface(expr_x, expr_y, expr_z, range_u, range_v, **kwargs)``
  711. If the ranges is not specified, then a default range of (-10, 10) is used.
  712. Multiple plots.
  713. ``plot3d_parametric_surface((expr_x, expr_y, expr_z, range_u, range_v), ..., **kwargs)``
  714. Ranges have to be specified for every expression.
  715. Default range may change in the future if a more advanced default range
  716. detection algorithm is implemented.
  717. Arguments
  718. =========
  719. expr_x : Expression representing the function along ``x``.
  720. expr_y : Expression representing the function along ``y``.
  721. expr_z : Expression representing the function along ``z``.
  722. range_u : (:class:`~.Symbol`, float, float)
  723. A 3-tuple denoting the range of the u variable, e.g. (u, 0, 5).
  724. range_v : (:class:`~.Symbol`, float, float)
  725. A 3-tuple denoting the range of the v variable, e.g. (v, 0, 5).
  726. Keyword Arguments
  727. =================
  728. Arguments for ``ParametricSurfaceSeries`` class:
  729. n1 : int
  730. The ``u`` range is sampled uniformly at ``n1`` of points.
  731. This keyword argument replaces ``nb_of_points_u``, which should be
  732. considered deprecated.
  733. n2 : int
  734. The ``v`` range is sampled uniformly at ``n2`` of points.
  735. This keyword argument replaces ``nb_of_points_v``, which should be
  736. considered deprecated.
  737. Aesthetics:
  738. surface_color : Function which returns a float
  739. Specifies the color for the surface of the plot. See
  740. :class:`~Plot` for more details.
  741. If there are multiple plots, then the same series arguments are applied for
  742. all the plots. If you want to set these options separately, you can index
  743. the returned ``Plot`` object and set it.
  744. Arguments for ``Plot`` class:
  745. title : str
  746. Title of the plot.
  747. size : (float, float), optional
  748. A tuple in the form (width, height) in inches to specify the size of the
  749. overall figure. The default value is set to ``None``, meaning the size will
  750. be set by the default backend.
  751. Examples
  752. ========
  753. .. plot::
  754. :context: reset
  755. :format: doctest
  756. :include-source: True
  757. >>> from sympy import symbols, cos, sin
  758. >>> from sympy.plotting import plot3d_parametric_surface
  759. >>> u, v = symbols('u v')
  760. Single plot.
  761. .. plot::
  762. :context: close-figs
  763. :format: doctest
  764. :include-source: True
  765. >>> plot3d_parametric_surface(cos(u + v), sin(u - v), u - v,
  766. ... (u, -5, 5), (v, -5, 5))
  767. Plot object containing:
  768. [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)
  769. See Also
  770. ========
  771. Plot, ParametricSurfaceSeries
  772. """
  773. args = _plot_sympify(args)
  774. plot_expr = _check_arguments(args, 3, 2, **kwargs)
  775. kwargs.setdefault("xlabel", "x")
  776. kwargs.setdefault("ylabel", "y")
  777. kwargs.setdefault("zlabel", "z")
  778. labels = kwargs.pop("label", [])
  779. rendering_kw = kwargs.pop("rendering_kw", None)
  780. series = _create_series(ParametricSurfaceSeries, plot_expr, **kwargs)
  781. _set_labels(series, labels, rendering_kw)
  782. plots = plot_factory(*series, **kwargs)
  783. if show:
  784. plots.show()
  785. return plots
  786. def plot_contour(*args, show=True, **kwargs):
  787. """
  788. Draws contour plot of a function
  789. Usage
  790. =====
  791. Single plot
  792. ``plot_contour(expr, range_x, range_y, **kwargs)``
  793. If the ranges are not specified, then a default range of (-10, 10) is used.
  794. Multiple plot with the same range.
  795. ``plot_contour(expr1, expr2, range_x, range_y, **kwargs)``
  796. If the ranges are not specified, then a default range of (-10, 10) is used.
  797. Multiple plots with different ranges.
  798. ``plot_contour((expr1, range_x, range_y), (expr2, range_x, range_y), ..., **kwargs)``
  799. Ranges have to be specified for every expression.
  800. Default range may change in the future if a more advanced default range
  801. detection algorithm is implemented.
  802. Arguments
  803. =========
  804. expr : Expression representing the function along x.
  805. range_x : (:class:`Symbol`, float, float)
  806. A 3-tuple denoting the range of the x variable, e.g. (x, 0, 5).
  807. range_y : (:class:`Symbol`, float, float)
  808. A 3-tuple denoting the range of the y variable, e.g. (y, 0, 5).
  809. Keyword Arguments
  810. =================
  811. Arguments for ``ContourSeries`` class:
  812. n1 : int
  813. The x range is sampled uniformly at ``n1`` of points.
  814. This keyword argument replaces ``nb_of_points_x``, which should be
  815. considered deprecated.
  816. n2 : int
  817. The y range is sampled uniformly at ``n2`` of points.
  818. This keyword argument replaces ``nb_of_points_y``, which should be
  819. considered deprecated.
  820. Aesthetics:
  821. surface_color : Function which returns a float
  822. Specifies the color for the surface of the plot. See
  823. :class:`sympy.plotting.Plot` for more details.
  824. If there are multiple plots, then the same series arguments are applied to
  825. all the plots. If you want to set these options separately, you can index
  826. the returned ``Plot`` object and set it.
  827. Arguments for ``Plot`` class:
  828. title : str
  829. Title of the plot.
  830. size : (float, float), optional
  831. A tuple in the form (width, height) in inches to specify the size of
  832. the overall figure. The default value is set to ``None``, meaning
  833. the size will be set by the default backend.
  834. See Also
  835. ========
  836. Plot, ContourSeries
  837. """
  838. kwargs.setdefault("show", show)
  839. return _plot3d_plot_contour_helper(ContourSeries, *args, **kwargs)
  840. def check_arguments(args, expr_len, nb_of_free_symbols):
  841. """
  842. Checks the arguments and converts into tuples of the
  843. form (exprs, ranges).
  844. Examples
  845. ========
  846. .. plot::
  847. :context: reset
  848. :format: doctest
  849. :include-source: True
  850. >>> from sympy import cos, sin, symbols
  851. >>> from sympy.plotting.plot import check_arguments
  852. >>> x = symbols('x')
  853. >>> check_arguments([cos(x), sin(x)], 2, 1)
  854. [(cos(x), sin(x), (x, -10, 10))]
  855. >>> check_arguments([x, x**2], 1, 1)
  856. [(x, (x, -10, 10)), (x**2, (x, -10, 10))]
  857. """
  858. if not args:
  859. return []
  860. if expr_len > 1 and isinstance(args[0], Expr):
  861. # Multiple expressions same range.
  862. # The arguments are tuples when the expression length is
  863. # greater than 1.
  864. if len(args) < expr_len:
  865. raise ValueError("len(args) should not be less than expr_len")
  866. for i in range(len(args)):
  867. if isinstance(args[i], Tuple):
  868. break
  869. else:
  870. i = len(args) + 1
  871. exprs = Tuple(*args[:i])
  872. free_symbols = list(set().union(*[e.free_symbols for e in exprs]))
  873. if len(args) == expr_len + nb_of_free_symbols:
  874. #Ranges given
  875. plots = [exprs + Tuple(*args[expr_len:])]
  876. else:
  877. default_range = Tuple(-10, 10)
  878. ranges = []
  879. for symbol in free_symbols:
  880. ranges.append(Tuple(symbol) + default_range)
  881. for i in range(len(free_symbols) - nb_of_free_symbols):
  882. ranges.append(Tuple(Dummy()) + default_range)
  883. plots = [exprs + Tuple(*ranges)]
  884. return plots
  885. if isinstance(args[0], Expr) or (isinstance(args[0], Tuple) and
  886. len(args[0]) == expr_len and
  887. expr_len != 3):
  888. # Cannot handle expressions with number of expression = 3. It is
  889. # not possible to differentiate between expressions and ranges.
  890. #Series of plots with same range
  891. for i in range(len(args)):
  892. if isinstance(args[i], Tuple) and len(args[i]) != expr_len:
  893. break
  894. if not isinstance(args[i], Tuple):
  895. args[i] = Tuple(args[i])
  896. else:
  897. i = len(args) + 1
  898. exprs = args[:i]
  899. assert all(isinstance(e, Expr) for expr in exprs for e in expr)
  900. free_symbols = list(set().union(*[e.free_symbols for expr in exprs
  901. for e in expr]))
  902. if len(free_symbols) > nb_of_free_symbols:
  903. raise ValueError("The number of free_symbols in the expression "
  904. "is greater than %d" % nb_of_free_symbols)
  905. if len(args) == i + nb_of_free_symbols and isinstance(args[i], Tuple):
  906. ranges = Tuple(*list(args[
  907. i:i + nb_of_free_symbols]))
  908. plots = [expr + ranges for expr in exprs]
  909. return plots
  910. else:
  911. # Use default ranges.
  912. default_range = Tuple(-10, 10)
  913. ranges = []
  914. for symbol in free_symbols:
  915. ranges.append(Tuple(symbol) + default_range)
  916. for i in range(nb_of_free_symbols - len(free_symbols)):
  917. ranges.append(Tuple(Dummy()) + default_range)
  918. ranges = Tuple(*ranges)
  919. plots = [expr + ranges for expr in exprs]
  920. return plots
  921. elif isinstance(args[0], Tuple) and len(args[0]) == expr_len + nb_of_free_symbols:
  922. # Multiple plots with different ranges.
  923. for arg in args:
  924. for i in range(expr_len):
  925. if not isinstance(arg[i], Expr):
  926. raise ValueError("Expected an expression, given %s" %
  927. str(arg[i]))
  928. for i in range(nb_of_free_symbols):
  929. if not len(arg[i + expr_len]) == 3:
  930. raise ValueError("The ranges should be a tuple of "
  931. "length 3, got %s" % str(arg[i + expr_len]))
  932. return args