Tutorial#

Computing contours of a custom function#

The function defines a 3d surface, for which we will compute its contours.

We start by importing our library and some plotting functions.

[1]:
import numpy as np

from fermi_contours import marching_squares as ms

from matplotlib import pyplot as plt
from matplotlib import colors
from mpl_toolkits.axes_grid1 import make_axes_locatable

[2]:
def plot_contours(squares, levels, contours, cmap=None, background_cmap="Blues"):
    if cmap is None:
        cmap = plt.cm.Oranges

    fig = plt.figure(figsize=plt.figaspect(2))
    ax1 = fig.add_subplot(2, 1, 1)
    ax2 = fig.add_subplot(2, 1, 2, projection='3d', computed_zorder=False)

    # 2d axis

    n = len(levels)
    color_list = cmap(np.linspace(0, 1, n))
    norm = colors.Normalize(vmin=min(levels), vmax=max(levels))

    divider = make_axes_locatable(ax1)
    cax1 = divider.new_horizontal(size="5%", pad=0.2)
    fig.add_axes(cax1)
    fig.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), cax=cax1)
    X, Y = np.meshgrid(*squares.grid_points)
    ax1.pcolormesh(X, Y, squares.grid_values.T, cmap=background_cmap);



    for i, contours_per_level in enumerate(contours):
        for contour in contours_per_level:

            ax1.plot(*np.array(contour).T, color=color_list[i])



    # 3d axis
    X, Y = np.meshgrid(*squares.grid_points)
    ax2.plot_surface(X, Y, squares.grid_values.T, alpha=1, cmap=background_cmap)
    ax2.view_init(azim=-80, elev=20)


    for i, (level, contours_per_level) in enumerate(zip(levels, contours)):
        for contour in contours_per_level:
            if len(contour) > 0:
                _x, _y = np.array(contour)[None, :].T
                _z = np.ones(len(_x))[:, None] * level
                ax2.plot(_x, _y, _z, color=color_list[i])

    return fig, ax1, ax2

The custom function can be anything that takes two floats and returns another float. If the function is expensive to compute in a serial way, consider computing the grid_values elsewhere, and provide the values instead of the function.

[3]:
def surface(x, y):
    return x ** 2 + y ** 2

To instantiate our MarchingSquares class, we need to provide the bounds of the grid. These bounds will be used to scale the position of the contours.

[4]:
squares = ms.MarchingSquares(
    func=surface,
    res=[20, 10],
    bounds=[[-2,2], [-1, 1]],
)

At this point, the squares instance has extracted the grid_points and their grid_values. If these are not provided, the grid_points are computed them using the res and bounds parameters, and the grid_values are the evaluations of func on those points.

[5]:
X, Y = np.meshgrid(*squares.grid_points)
plt.pcolormesh(X, Y, squares.grid_values.T, cmap="Blues");

_images/tutorial_10_0.png

Computing contours#

Now, we just call our instance with the values of the levels for which we want to compute the contour.

[6]:
levels = np.linspace(0, 1, 21)
levels

[6]:
array([0.  , 0.05, 0.1 , 0.15, 0.2 , 0.25, 0.3 , 0.35, 0.4 , 0.45, 0.5 ,
       0.55, 0.6 , 0.65, 0.7 , 0.75, 0.8 , 0.85, 0.9 , 0.95, 1.  ])
[7]:
contours = [squares(l) for l in levels]

To visualize the contours, we can plot the grid_values in blue, and the set of contours in orange.

[8]:
plot_contours(squares, levels=levels, contours=contours)

[8]:
(<Figure size 400x800 with 3 Axes>, <Axes: >, <Axes3D: >)
_images/tutorial_15_1.png

Special cases#

Open contours#

It may happen that our contour is not closed, or at least not within the bounds that we want. In this case, we can set the parameter open_contours=True to allow this behaviour. This can also lead to more than one contour per level

[9]:
levels_open = np.linspace(0, 5, 21)
levels_open

[9]:
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  , 2.25, 2.5 ,
       2.75, 3.  , 3.25, 3.5 , 3.75, 4.  , 4.25, 4.5 , 4.75, 5.  ])
[10]:
contours_open = [squares(l) for l in levels_open]

To visualize the contours, we can plot the grid_values in blue, and the set of contours in orange.

[11]:
plot_contours(squares, levels=levels_open, contours=contours_open)

[11]:
(<Figure size 400x800 with 3 Axes>, <Axes: >, <Axes3D: >)
_images/tutorial_20_1.png

Periodic boundaries#

If we want that contours wrap around the edges of our grid, we can set the parameter periodic=True.

To make use of this functionality, let’s define a periodic function and plot the contours.

[12]:
def surface_periodic(x, y):
    period = np.pi
    return np.sin(period * x / 2) + np.sin(period * y )

[13]:
squares_periodic = ms.MarchingSquares(
    func=surface_periodic,
    res=[20, 10],
    bounds=[[-2,2], [-1, 1]],
    periodic=False,
)

[14]:
levels_periodic = np.linspace(-2, 2, 21)
levels_periodic

[14]:
array([-2. , -1.8, -1.6, -1.4, -1.2, -1. , -0.8, -0.6, -0.4, -0.2,  0. ,
        0.2,  0.4,  0.6,  0.8,  1. ,  1.2,  1.4,  1.6,  1.8,  2. ])
[15]:
contours_periodic = [squares_periodic(l) for l in levels_periodic]

[16]:
plot_contours(squares_periodic, levels=levels_periodic, contours=contours_periodic, cmap=plt.cm.PuOr)

[16]:
(<Figure size 400x800 with 3 Axes>, <Axes: >, <Axes3D: >)
_images/tutorial_26_1.png