Leon Avery
Leon Avery

Reputation: 866

2D Interpolation with periodic boundary conditions

I'm running a simulation on a 2D space with periodic boundary conditions. A continuous function is represented by its values on a grid. I need to be able to evaluate the function and its gradient at any point in the space. Fundamentally, this isn't a hard problem -- or to be precise, it's an almost already solved problem. The function can be interpolated using a cubic spline with scipy.interpolate.RectBivariateSpline. The reason it's almost solved is that RectBivariateSpline cannot handle periodic boundary conditions, nor can anything else in scipy.interpolate, as far as I can figure out from the documentation.

Is there a python package that can do this? If not, can I adapt scipy.interpolate to handle periodic boundary conditions? For instance, would it be enough to put a border of, say, four grid elements around the entire space and explicitly represent the periodic condition on it?

[ADDENDUM] A little more detail, in case it matters: I am simulating the motion of animals in a chemical gradient. The continuous function I mentioned above is the concentration of a chemical that they are attracted to. It changes with time and space according to a straightforward reaction/diffusion equation. Each animal has an x,y position (which cannot be assumed to be at a grid point). They move up the gradient of attractant. I'm using periodic boundary conditions as a simple way of imitating an unbounded space.

Upvotes: 7

Views: 4641

Answers (4)

Till Hoffmann
Till Hoffmann

Reputation: 9877

I have been using the following function which augments the input to create data with effective periodic boundary conditions. Augmenting the data has a distinct advantage over modifying an existing algorithm: the augmented data can easily be interpolated using any algorithm. See below for an example.

def augment_with_periodic_bc(points, values, domain):
    """
    Augment the data to create periodic boundary conditions.

    Parameters
    ----------
    points : tuple of ndarray of float, with shapes (m1, ), ..., (mn, )
        The points defining the regular grid in n dimensions.
    values : array_like, shape (m1, ..., mn, ...)
        The data on the regular grid in n dimensions.
    domain : float or None or array_like of shape (n, )
        The size of the domain along each of the n dimenions
        or a uniform domain size along all dimensions if a 
        scalar. Using None specifies aperiodic boundary conditions.

    Returns
    -------
    points : tuple of ndarray of float, with shapes (m1, ), ..., (mn, )
        The points defining the regular grid in n dimensions with 
        periodic boundary conditions.
    values : array_like, shape (m1, ..., mn, ...)
        The data on the regular grid in n dimensions with periodic
        boundary conditions.
    """
    # Validate the domain argument
    n = len(points)
    if np.ndim(domain) == 0:
        domain = [domain] * n
    if np.shape(domain) != (n,):
        raise ValueError("`domain` must be a scalar or have the same "
                         "length as `points`")

    # Pre- and append repeated points
    points = [x if d is None else np.concatenate([x - d, x, x + d]) 
              for x, d in zip(points, domain)]

    # Tile the values as necessary
    reps = [1 if d is None else 3 for d in domain]
    values = np.tile(values, reps)

    return points, values

Example

The example below shows interpolation with periodic boundary conditions in one dimension but the function above can be applied in arbitrary dimensions.

example of periodic interpolation

rcParams['figure.dpi'] = 144
fig, axes = plt.subplots(2, 2, True, True)

np.random.seed(0)
x = np.linspace(0, 1, 10, endpoint=False)
y = np.sin(2 * np.pi * x)
ax = axes[0, 0]
ax.plot(x, y, marker='.')
ax.set_title('Points to interpolate')

sampled = np.random.uniform(0, 1, 100)
y_sampled = interpolate.interpn([x], y, sampled, bounds_error=False)
valid = ~np.isnan(y_sampled)
ax = axes[0, 1]
ax.scatter(sampled, np.where(valid, y_sampled, 0), marker='.', c=np.where(valid, 'C0', 'C1'))
ax.set_title('interpn w/o periodic bc')

[x], y = augment_with_periodic_bc([x], y, domain=1.0)
y_sampled_bc = interpolate.interpn([x], y, sampled)
ax = axes[1, 0]
ax.scatter(sampled, y_sampled_bc, marker='.')
ax.set_title('interpn w/ periodic bc')

y_sampled_bc_cubic = interpolate.interp1d(x, y, 'cubic')(sampled)
ax = axes[1, 1]
ax.scatter(sampled, y_sampled_bc_cubic, marker='.')
ax.set_title('cubic interp1d w/ periodic bc')

fig.tight_layout()

Upvotes: 0

Alexander McFarlane
Alexander McFarlane

Reputation: 11293

These functions can be found at my github, master/hmc/lattice.py:

  • Periodic boundary conditions The Periodic_Lattice() class is described here in full.
  • Lattice Derivatives In the repository you will find a laplacian function, a squared gradient (for the gradient just take the square root) and and overloaded version of np.ndarray
  • Unit Tests The test cases can be found in same repo in tests/test_lattice.py

Upvotes: 0

Filipe Maia
Filipe Maia

Reputation: 76

Another function that could work is scipy.ndimage.interpolation.map_coordinates. It does spline interpolation with periodic boundary conditions. It does not not directly provide derivatives, but you could calculate them numerically.

Upvotes: 1

Leon Avery
Leon Avery

Reputation: 866

It appears that the python function that comes closest is scipy.signal.cspline2d. This is exactly what I want, except that it assumes mirror-symmetric boundary conditions. Thus, it appears that I have three options:

  1. Write my own cubic spline interpolation function that works with periodic boundary conditions, perhaps using the cspline2d sources (which are based on functions written in C) as a starting point.

  2. The kludge: the effect of data at i on the spline coefficient at j goes as r^|i-j|, with r = -2 + sqrt(3) ~ -0.26. So the effect of the edge is down to r^20 ~ 10^-5 if I nest the grid within a border of width 20 all the way around that replicates the periodic values, something like this:

    bzs1 = np.array( [zs1[i%n,j%n] for i in range(-20, n+20) for j in range(-20, n+20)] )
    bzs1 = bzs1.reshape((n + 40, n + 40))

    Then I call cspline2d on the whole array, but use only the middle. This should work, but it's ugly.

  3. Use Hermite interpolation instead. In a 2D regular grid, this corresponds to bicubic interpolation. The disadvantage is that the interpolated function has a discontinuous second derivative. The advantages are it is (1) relatively easy to code, and (2) for my application, computationally efficient. At the moment, this is the solution I'm favoring.

I did the math for interpolation with trig functions rather than polynomials, as @mdurant suggested. It turns out to be very similar to the cubic spline, but requires more computation and produces worse results, so I won't be doing that.

EDIT: A colleague told me of a fourth solution:

  1. The GNU Scientific Library (GSL) has interpolation functions that can handle periodic boundary conditions. There are two (at least) python interfaces to GSL: PyGSL and CythonGSL. Unfortunately, GSL interpolation seems to be restricted to one dimension, so it's not a lot of use to me, but there's lots of good stuff in GSL.

Upvotes: 8

Related Questions