Demo 1: Functions, Plotting, and Partial Derivatives#

Demo by Christian Mikkelstrup and Hans Henrik Hermansen. Revised March 2026 by shsp.

from sympy import *
from dtumathtools import *
init_printing()

Welcome back after Christmas and January and welcome to a Spring with Mathematics 1b. There will be a whole lot of new mathematical curriculum, and among other things a whole lot of 3D plots! For this we have developed the Python library dtumathtools, which will accompany you during the semester. It contains dtuplot, designed for efficient plotting, as well as several good helper functions. You should have dtumathtools installed on your computer already from Mathematics a; if not, then run the command in a terminal:

conda install dtumathtools==2025.2.0   # Replace conda with pip or pip3 if you use those tools for library import instead

A Function in One Variable#

Defining Functions and Evaluating Values#

We can define a mathematical function, such as \(f: \mathbb{R} \to \mathbb{R}\), \(f(x)=x \operatorname{e}^x\), as a Python function using the following familiar structure with the def command:

def f(x):  
    return x * exp(x)

\(f\) is then evaluated at the point \(x = -2\) by typing:

f(-2)
../_images/b6e1b3b9d8e202066faa6446674f62366158552c856671c27ccc663bd775c0e1.png

whose decimal value is:

f(-2).evalf()
../_images/0444f753e37329b0613e8fff94ae9ccaa2eb1de475d9d23016b063931a0bcd06.png

It is often not necessary to define our mathematical functions as Python functions with the def command. We might prefer simply saving the functional expression as a symbol:

x = symbols('x', real = True)   # Define x as a symbolic (Sympy) variable (we apply the assumption real=True, since R -> R, although this is not strictly necessary here)
f_expr = x * exp(x)
f_expr
../_images/3ae31244e67473a5ac8fc7ece285237016df4d71318e05f3b42e4411dd04e939.png

This time, typing f(-2) of course won’t work so we will instead use a substitution:

f_expr.subs(x, -2)
../_images/b6e1b3b9d8e202066faa6446674f62366158552c856671c27ccc663bd775c0e1.png

Note that a Python function and a mathematical function conceptually are different things (a Python function doesn’t have to represent a mathematical function). Our use of the term “function” in these demos will always refer to a mathematical function, and we will explicitly write “Python function” otherwise.

Derivatives, Limits, and Plots#

The function \(f\) can be differentiated by:

f_prime = f_expr.diff(x)
f_prime
../_images/fd6bd66de492f2b223cfc4a096eef0ba3367ce8aaa240228948628894e0e113d.png

We can investigate limits, such as for \(x \to -\infty\), \(x \to \infty\), and \(x \to -2\), with the commands:

f_expr.limit(x, -oo), f_expr.limit(x, oo), f_expr.limit(x, -2)
../_images/23060d86505aca9cf523745af7a47c60d34e380346bff1b0fe404cc979edc179.png

It should be no surprise that \(\displaystyle\lim_{x \to -2} f(x) = f(-2)\), since the function is continuous.

We can plot the graphs of the function and its derivative in the same window by:

plot(f_expr, f_prime, (x, -5, 1))
../_images/0f753bdb9fb0ed2f99749bbee40d6f39133746d2facab14bb628a6b84f08e887.png
<sympy.plotting.backends.matplotlibbackend.matplotlib.MatplotlibBackend at 0x7f8c150dab50>

Compare this plot from the built-in plot command with the following from dtuplot:

dtuplot.plot(f_expr, f_prime, (x, -5, 1))
../_images/c6d7f103ab6a2ffaa1957c8f88ecf7e08dab3e9a7169bd7d85fa9f889e6db490.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f8c15fb6d10>

We see that certain plot properties are set by default, such as a legend and a grid.

Piecewise-Defined Functions#

A little more complicated example could be the piecewise-defined function \(g: \mathbb{R} \to \mathbb{R}\),

\[\begin{equation*} g(x) = \begin{cases} -x & ,x <0 \\ \operatorname{e}^x & ,x \ge 0 \end{cases}\,\,, \end{equation*}\]

which is harder to directly save as a simple symbol. For this, we might prefer defining it as a Python function by:

def g(x):
    if x < 0:
        return -x
    else:
        return exp(x)

But with the Sympy library at hand, we can in this specific case avoid Python function definitions by using the convenient command:

g_expr = Piecewise((-x, x < 0), (exp(x), x >= 0))
g_expr
\[\begin{split}\displaystyle \begin{cases} - x & \text{for}\: x < 0 \\e^{x} & \text{otherwise} \end{cases}\end{split}\]

which evaluates as expected:

g_expr.subs(x, -2)
../_images/92f88f218e4707cb362e045ff538e4563ffc87ee097cc6f41c0154b31fb249ec.png

Plotting a piecewise-defined function is no different from plotting other functions:

dtuplot.plot(g_expr,(x,-5,2), ylabel='g(x)')
../_images/da10b4653020a5b1a3f1e5d0c5641ac7bfa0eb54422f1614a7993ee9aca38bf8.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f8c12f6a150>

The graph indicates a discontinuity (a “jump” within the domain) at \(x = 0\). Keep in mind, though, that seeing this on a graph is just an indication; we cannot draw this conclusion based purely on what we see on a plot, as we might be tricked by an unlucky zoom level, camera orientation or more. Unfortunately, Python/CAS does not have any way of proving or disproving continuity for us. For example, although a function isn’t differentiable at a discontinuous point, Sympy will carry out differentiation without raising any suspicion:

g_expr.diff(x)
\[\begin{split}\displaystyle \begin{cases} -1 & \text{for}\: x < 0 \\e^{x} & \text{otherwise} \end{cases}\end{split}\]

For proofs of continuity or differentiability, the manual way is the certain way, e.g. disproving by finding a counterexample or proving using an epsilon-delta argument and the like.

Partial Derivatives using diff#

For functions of multiple variables we will now introduce the partial derivative. Consider the function:

x, y = symbols('x y')
f = x*y**2+x
f
../_images/47ad93ddbee374ec0abaa43a424c1ada0278074ee6ec31a728f6906f737db93f.png

We now differentiate it using the diff command, but since we are dealing with a function of two variables we must choose with respect to which variable we wish to differentiate:

f.diff(x), f.diff(y)
../_images/96cdb955fa422f69b2de23ee99067a79352c90072210d65e88d0b349fe1aee26.png

These are the partial derivatives, \(\frac{\partial f}{\partial x}\) and \(\frac{\partial f}{\partial y}\). Each partial derivative can of course be differentiated one more time with respect to each variable, yielding the second-order partial derivatives. This function of two variables has four such second-order partial derivatives to consider:

f.diff(x).diff(x), f.diff(x).diff(y), f.diff(y).diff(x), f.diff(y).diff(y)
../_images/0896bd82a49855d4c2a2bb44e08ac4a050c613164395f9383f164122b48fb026.png

More directly, these can also be found by:

f.diff(x,2), f.diff(y,2), f.diff(x,y), f.diff(y,x)
../_images/eb575a275da7132aa8134dffece6afeef13b6639d4f483f9bbce8100bfce913e.png

We can substitute in values for \(x\) and \(y\) and calculate, for example, \(\frac{\partial}{\partial x}f(-2,3)\):

f.diff(x).subs({x:-2,y:3})
../_images/bb054c8c986ed7dab843f2368babfeb4b847485be1bc4d64c526942ba1d97770.png

or \(\frac{\partial^2}{\partial y\partial x}f(5,-13)\):

f.diff(x,y).subs({x:5,y:-13})
../_images/525da790f07f0ade39f9e268f9151abed4a5e2e5dc2551b651c54546a2cdda2c.png

Plots#

Changing Orientation#

We will now consider plots of functions in two variables, hence plotting in 3D! (Note that with more than two variables, a plot of the graph requires more than three axes, making plotting the graph impossible.) Plotting graphs in 3D is easy, and we can even choose from which angle to view them. By default, dtuplot will choose a fitting angle, but if we wish to inspect the graph from some other chosen angle, then camera can be used. Try changing the values of elev and azim in the follow plot command:

f = 4-x**2-y**2

p=dtuplot.plot3d(f, (x,-3,3),(y,-3,3), camera = {"elev": 25, "azim": 45})
../_images/a0c34af871b5a0e78cbbe2cdd96d7e465c765749b5e3f62731e287cd06da0f90.png

Interactive Plots#

The plot above was generated as a static PNG-file, which is great if you wish to print or export the Notebook as a PDF, or if you need to copy the plot as a image to your project report or so. All plots will be static if we don’t do anything or if we use the command %matplotlib inline.

If we instead run the command %matplotlib qt (which in the following cell has been commented out; try running the cell after removing #), then we enable interactive plots. All subsequent plots, will then “pop out” of our VS Code editor and can be rotated by pressing-and-dragging with the cursor.

# %matplotlib qt

Running this command changes the backend of matplotlib to Qt, affecting the entire Jupyter kernel, which means that all plots from now on will open in a separate window rather than being printed directly on the Jupyter Notebook page. This affects all later plots in the same session, also other Jupyter Notebooks that use the same kernel, but is not permanent and will be reset when the kernel is restarted. To switch back to default (inline) plotting without a kernel restart, simply run the command: %matplotlib inline.

Note that %matplotlib qt only works when running Python on your own computer. It will, for instance, not work if you run Python on an online server like Google Colab. In such cases you must use widgets instead, such as %matplotlib ipympl. However, this requires installation of an extra package, in this case ipympl.

Overview of commands:

# %matplotlib inline        # For static plots
# %matplotlib qt            # QT (cute) for interactive "pop-out" plots
# %matplotlib ipympl        # Widget/ipynpl for interactive inline plots (not as stable as QT, may require a restart of the kernel)
# %matplotlib --list        # List of all backends

Aesthetics#

Changing how plots generated with dtuplot look can be done by setting rendering keywords with rendering_kw={...}. With this, you can specify rendering settings for color, alpha (transparency), and more:

dtuplot.plot3d(f, (x,-3,3),(y,-3,3), wireframe = True, rendering_kw = {"color": "red", "alpha": 0.5})
../_images/b3ba44790fdf1a217b5faaf85dd44324e4285ba727182083c2d2d5430d11e40b.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f8c1534c710>

Some aesthetic choices are special enough to have their own argument outside of rendering_kw, such as wireframe as used in the above plot, and use_cm which activates a color mapping in the below plot:

p=dtuplot.plot3d(f, (x,-3,3),(y,-3,3), use_cm=True, legend=True)
../_images/adca93f71d72443ea4b69c653a03b5862d2751baf22ab688a9b32a79cec43139.png

Level Sets#

When dealing with functions of two or three variables we might prefer visualizing the function with a counter plot (that is, plotting level sets) as an alternative to plotting the graph. Note the difference in dimensionality: plotting the graph of scalar functions requires an extra axis on top of the axes for each variable, which is not the case for a contour plot of level sets. For a function in two variables, a contour plot will be 2D, whereas a plot of its graph will be a 3D plot:

dtuplot.plot_contour(f, (x,-3,3),(y,-3,3), is_filled=False)
../_images/c9542a91aaa9c03973320cd809f1253e5ee129cb33920e1d5f6ff5a7bbd747da.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f8bf00bdd90>

We can choose the exact function values to show level curves for (just remember that in order for contour plots to properly visualize “steepness”, you will want an even spacing between all levels):

z_levels = [-2,-1,0,1]
dtuplot.plot_contour(f, (x,-3,3),(y,-3,3), rendering_kw={"levels":z_levels, "alpha":0.5}, is_filled=False)
../_images/27f7953644264be8c08db016e81b40e7511e046ef237e44b3eddc4a564c7bc75.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f8bf216ec10>

The Gradient and Gradient Vector Fields#

Consider the function \(f:\mathbb R^2\to\mathbb R\):

(4)#\[\begin{equation} f(x,y)=\cos(x)+\sin(y). \end{equation}\]

It’s graph can be visualized as a 2-dimensional surface in a 3D coordinate system:

f = cos(x)+sin(y)
p = dtuplot.plot3d(f, (x,-pi/2,3/2*pi),(y,0,2*pi),use_cm=True, camera={"elev":45, "azim":-65}, legend=True)
../_images/afc3ef5ef825f19b779ced18bfc4ca848a23fcad47c5929b56bb3c8166be7285.png

The gradient of \(f\) at a point \((x,y)\) is a vector that we symbolize with the nabla symbol as \(\nabla f(x,y)\). It is constructed from the two partial derivatives of \(f\) as follows:

nf = Matrix([f.diff(x), f.diff(y)])
nf
\[\begin{split}\displaystyle \left[\begin{matrix}- \sin{\left(x \right)}\\\cos{\left(y \right)}\end{matrix}\right]\end{split}\]

The gradient can also be computed using dtutools.gradient (although it should be noted that we with this command not always have full control over the order in which the variables are used).

dtutools.gradient(f)
\[\begin{split}\displaystyle \left[\begin{matrix}- \sin{\left(x \right)}\\\cos{\left(y \right)}\end{matrix}\right]\end{split}\]

The above gradient expression defines a vector at every point, which means that we can consider this gradient as a vector function \(\nabla f:\mathbb R^2\to \mathbb R^2\) expressing a set of vectors spread over the \((x,y)\) plane. This is called a vector field, in this case particularly a gradient vector field. We plot this as follows:

dtuplot.plot_vector(nf, (x,-pi/2,3/2*pi),(y,0,2*pi),scalar=False)
../_images/45c390f25fec5ef29bc181c4c5453ea218e3dc389133deaaa5009962eb5e0f67.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f8bf03c5b10>

or if we want to get fancy (note that the aesthetics keywords have been separated such that arrows and contours are styled separately, giving more control):

dtuplot.plot_vector(nf, (x,-pi/2,3/2*pi),(y,0,2*pi),
    quiver_kw={"color":"black"},
    contour_kw={"cmap": "Blues_r", "levels": 20},
    grid=False, xlabel="x", ylabel="y",n=15)
../_images/888515e82afbb872625365879d23df3a0e82193462dff4c4745cc615bb00c8a4.png
<spb.backends.matplotlib.matplotlib.MatplotlibBackend at 0x7f8beb6b3d10>