=========
Overview
=========
The following overview briefly introduce the functionalities exposed by this
module.
Plotting functions
==================
On top of the usual and limited SymPy plotting functions, many new functions
are implemented to deal with 2D or 3D lines, contours, surfaces, vectors,
complex functions and control theory. The output of all of them can be viewed
by exploring the :doc:`Modules ` section.
Backends
========
This module allows the user to chose between 5 different backends (plotting
libraries):
`Matplotlib `_,
`Plotly `_,
`Bokeh `_,
`K3D-Jupyter `_.
The 3 most important reasons for supporting multiple backends are:
#. **In the Python ecosystem there is no perfect plotting library**. Each one
is great at something and terrible at something else. Supporting multiple
backends allows the plotting module to have a greater capability of
visualizing different kinds of symbolic expressions.
#. **Better interactive** experience (explored in the tutorial section), which
translates to better data exploration and visualization (especially when
working with Jupyter Notebook).
#. To use the **plotting library we are most comfortable with**.
More information about the backends can be found at:
:doc:`Backends ` .
Examples
========
The following code blocks shows a few examples about the capabilities of
this module. Please, try them on a Jupyter Notebook to explore the interactive
figures.
Interactive-Parametric 2D plot of the magnitude of a second order transfer
function:
.. panel-screenshot::
from sympy import symbols, log, sqrt, re, im, I
from spb import plot, BB
from bokeh.models.formatters import PrintfTickFormatter
formatter = PrintfTickFormatter(format="%.3f")
kp, t, z, o = symbols("k_P, tau, zeta, omega")
G = kp / (I**2 * t**2 * o**2 + 2 * z * t * o * I + 1)
mod = lambda x: 20 * log(sqrt(re(x)**2 + im(x)**2), 10)
plot(
(mod(G.subs(z, 0)), (o, 0.1, 100), "G(z=0)", {"line_dash": "dotted"}),
(mod(G.subs(z, 1)), (o, 0.1, 100), "G(z=1)", {"line_dash": "dotted"}),
(mod(G), (o, 0.1, 100), "G"),
params = {
kp: (1, 0, 3),
t: (1, 0, 3),
z: (0.2, 0, 1, 200, formatter, "z")
},
backend = BB,
n = 2000,
xscale = "log",
xlabel = "Frequency, omega, [rad/s]",
ylabel = "Magnitude [dB]",
use_latex = False,
update_event=True
)
Polar animation with Matplotlib:
.. code-block::
from sympy import symbols, sin, cos, pi, latex
from spb import plot_polar, prange
u, x = symbols("u, x")
expr = sin(2 * x) * cos(5 * x) + pi / 2
plot_polar(
expr, prange(x, 0, u),
params={u: (1, 2*pi)}, animation={"fps": 10, "time": 4},
polar_axis=True, ylim=(0, 3), title="$%s$" % latex(expr))
.. image:: _static/animations/polar-animation.gif
:width: 500
:alt: polar animation
2D parametric plot with Matplotlib, using Numpy and lambda functions:
.. plot::
:context: reset
:include-source: True
import numpy as np
from spb import plot_parametric, multiples_of_pi_over_3
plot_parametric(
lambda t: np.sin(3 * t + np.pi / 4), lambda t: np.sin(4 * t),
("t", 0, 2 * np.pi), "t [rad]", xlabel="x", ylabel="y", aspect="equal",
colorbar_ticks_formatter=multiples_of_pi_over_3())
Interactive-Parametric domain coloring plot of a complex function:
.. panel-screenshot::
:small-size: 800, 625
from sympy import symbols, latex
from spb import *
import colorcet
u, v, w, z = symbols("u, v, w, z")
expr = (z - 1) / (u * z**2 + v * z + w * 1)
params = {
u: (1, 1e-5, 2),
v: (1, 0, 2),
w: (1, 0, 2),
}
graphics(
domain_coloring(
expr, (z, -2-2j, 2+2j), coloring="m", cmap=colorcet.CET_C7,
n=500, params=params),
use_latex=False, title="$%s$" % latex(expr), grid=False,
update_event=True
)
Animation of a 3D surface using K3D-Jupyter. Here we create an
``Animation`` object, which can later be used to save the animation
to a file.
.. code-block::
from sympy import *
from spb import *
import numpy as np
r, theta, t, a = symbols("r, theta, t, a")
expr = cos(r**2 - a) * exp(-r / 3)
plot3d_revolution(
expr, (r, 0, 5), (theta, 0, t),
params={t: (1e-03, 2*pi), a: (0, 2*pi)},
use_cm=True, color_func=lambda x, y, z: np.sqrt(x**2 + y**2),
is_polar=True,
wireframe=True, wf_n1=30, wf_n2=30,
wf_rendering_kw={"width": 0.005},
animation=True,
title=(r"theta={:.4f}; \, a={:.4f}", t, a),
backend=KB, grid=False
)
.. video:: _static/animations/3d_animation.mp4
:width: 500
3D plot with Plotly of a parametric surface, colored according to the
radius, with wireframe lines (also known as grid lines) highlighting the
parameterization:
.. plotly::
:camera: 1.75, 0, 0, 0, 0, 0, 0, 0, 1
from sympy import symbols, cos, sin, pi
from spb import plot3d_parametric_surface, PB
import numpy as np
u, v = symbols("u, v")
def trefoil(u, v, r):
x = r * sin(3 * u) / (2 + cos(v))
y = r * (sin(u) + 2 * sin(2 * u)) / (2 + cos(v + pi * 2 / 3))
z = r / 2 * (cos(u) - 2 * cos(2 * u)) * (2 + cos(v)) * (2 + cos(v + pi * 2 / 3)) / 4
return x, y, z
plot3d_parametric_surface(
trefoil(u, v, 3), (u, -pi, 3*pi), (v, -pi, 3*pi), "radius",
grid=False, title="Trefoil Knot", backend=PB, use_cm=True,
color_func=lambda x, y, z: np.sqrt(x**2 + y**2 + z**2),
wireframe=True, wf_n1=100, wf_n2=30, n1=250)
Riemann surface of ``Log(z)``, colored by its argument, using Plotly:
.. plotly::
:camera: 1.25, -1.25, 1, 0, 0, -0.3, 0, 0, 1
from sympy import *
from spb import *
r, theta = symbols("r theta")
graphics(
surface_parametric(
r * cos(theta), r * sin(theta), theta,
(r, 0, 2), (theta, -4*pi, 4*pi),
n1 = 10, n2=1000,
wireframe=True, wf_n1=6, wf_n2=40,
use_cm=True,
rendering_kw={"colorscale": "mygbm", "cmin": 0, "cmax": 2*np.pi},
color_func=lambda x, y, z, r, theta: theta % (2 * np.pi),
label="theta [rad]",
colorbar_ticks_formatter=multiples_of_pi_over_4(label="π")
),
backend=PB,
aspect=dict(x=1, y=1, z=1.15),
xlabel="Real", ylabel="Imag", zlabel="Im(Log(z))"
)
Visualizing a 2D vector field:
.. plotly::
from sympy import *
from spb import *
x, y = symbols("x, y")
expr = Tuple(1, sin(x**2 + y**2))
l = 2
plot_vector(
expr, (x, -l, l), (y, -l, l),
backend=PB, streamlines=True, scalar=False,
stream_kw={"line_color": "black", "density": 1.5},
xlim=(-l, l), ylim=(-l, l),
title=r"$\vec{F} = " + latex(expr) + "$")
Visualizing a 3D vector field with a random number of streamtubes:
.. k3d-screenshot::
:camera: 40.138, -37.134, 35.253, 4.387, -4.432, 25.837, 0.338, 0.513, 0.789
from sympy import *
from spb import *
var("x:z")
l = 30
u = 10 * (y - x)
v = 28 * x - y - x * z
w = -8 * z / 3 + x * y
plot_vector(
[u, v, w], (x, -l, l), (y, -l, l), (z, 0, 50),
backend=KB, n=50, grid=False, use_cm=False, streamlines=True,
stream_kw={"starts": True, "npoints": 15},
title="Lorentz \, attractor"
)
Visualizing the surface of a cone with outward pointing normal vectors.
.. k3d-screenshot::
from sympy import tan, cos, sin, pi, symbols
from spb import *
from sympy.vector import CoordSys3D, gradient
u, v = symbols("u, v")
N = CoordSys3D("N")
i, j, k = N.base_vectors()
xn, yn, zn = N.base_scalars()
t = 0.35 # half-cone angle in radians
expr = -xn**2 * tan(t)**2 + yn**2 + zn**2 # cone surface equation
g = gradient(expr)
n = g / g.magnitude() # unit normal vector
n1, n2 = 10, 20 # number of discretization points for the vector field
# cone surface to discretize vector field (low numb of discret points)
cone_discr = surface_parametric(
u / tan(t), u * cos(v), u * sin(v), (u, 0, 1), (v, 0 , 2*pi),
n1=n1, n2=n2)[0]
graphics(
surface_parametric(
u / tan(t), u * cos(v), u * sin(v), (u, 0, 1), (v, 0 , 2*pi),
rendering_kw={"opacity": 1}, wireframe=True,
wf_n1=n1, wf_n2=n2, wf_rendering_kw={"width": 0.004}),
vector_field_3d(
n, range1=(xn, -5, 5), range2=(yn, -5, 5), range3=(zn, -5, 5),
use_cm=False, slice=cone_discr,
quiver_kw={"scale": 0.5, "pivot": "tail"}
),
backend=KB, grid=False
)
Differences with sympy.plotting
===============================
* While the backends implemented in this module might resemble the ones from
the `sympy.plotting` module, they are not interchangeable.
* The ``plot_implicit`` function uses a mesh grid algorithm and contour plots
by default (in contrast to the adaptive algorithm used by `sympy.plotting`).
It is going to automatically switch to an adaptive algorithm if
Boolean expressions are found. This ensures a better visualization for
non-Boolean implicit expressions.
* `sympy.plotting` is unable to visualize summations containing infinity in
their lower/upper bounds. This module introduces the ``sum_bound`` keyword
argument into the ``plot`` function: it substitutes infinity with a large
integer number. As such, it is possible to visualize summations.
* `sympy.plotting` provides an adaptive algorithm for line plots. This module
does not.
* `sympy.plotting` exposed the ``nb_of_points_*`` keyword arguments. These have
been replaced with ``n`` or ``n1, n2``.
* `sympy.plotting` exposed the ``TextBackend`` class to create very basic
plots on a terminal window. This module removed it.
The following example compares how to customize a plot created with
`sympy.plotting` and one created with this module.
This is pretty much all we can do with `sympy.plotting`:
.. code-block:: python
from sympy.plotting import plot
from sympy import symbols, sin, cos
x = symbols("x")
p = plot(sin(x), cos(x), show=False)
p[0].label = "a"
p[0].line_color = "red"
p[1].label = "b"
p.show()
The above command works perfectly fine also with this new module. However,
we can customize the plot even further. In particular:
* it is possible to set a custom label directly from any plot function.
* the full potential of each backend can be accessed by providing
dictionaries containing backend-specific keyword arguments.
.. code-block:: python
from spb import plot
from sympy import symbols, sin, cos
x = symbols("x")
# pass customization options directly to matplotlib (or other backends)
plot(
(sin(x), "a", dict(color="k", linestyle=":")),
(cos(x), "b"),
backend=MB)
# alternatively, set the label and rendering_kw keyword arguments
# to lists: each element target an expression
# plot(sin(x), cos(x), label=["a", "b"], rendering_kw=[dict(color="k", linestyle=":"), None])
Read the documentation to learn how to further customize the appearance of
figures.
Take a look at :doc:`Modules ` for more examples about the output of this module.